#!/usr/bin/env python
# -*- coding: utf-8 -*-

#Lista de tipos de datos
tipo_datos = ['INTEGER', 'SHORTINT', 'SMALLINT', 'ORDINTEGER', 'BYTE', 'LONGINT', 'INT64', 'LONGWORD', 'WORD', 'CARDINAL' #ENTEROS
              'COMP', 'REAL48', 'REAL', 'SINGLE', 'DOUBLE', 'EXTENDED', 'CURRENCY', 'VARIANT', 'OLEVARIANT',        #REALES
              'STRING', 'CHAR',                                                                                     #STRINGS
              'BOOLEAN']                                                                                            #BOOLEANO
#Codificacion de datos
cod_datos = { "INTEGER": "10", "SHORTINT": "11", "SMALLINT": "12", "ORDINTEGER": "13", "BYTE": "14", "LONGINT": "15", "INT64": "16",
              "LONGWORD" : "17", "WORD" : "18", "CARDINAL" : "19",
              "REAL": "20", "REAL48": "21", "COMP": "22", "SINGLE": "23", "DOUBLE": "24", "EXTENDED": "25", "CURRENCY": "26",
              "VARIANT": "27", "OLEVARIANT": "28",
              "STRING": "30",
              "CHAR": "41",
              "BOOLEAN": "50" }

dicc_type = {}
#Guarda el tipo de retorno, trabajamos solo INTEGER, STRING, CHAR, REAL, BOOLEAN
nuevo_tipo = ""
#Operaciones compatibles
compatible = {}
compatible["+"] = [ ("1","1","INTEGER"), ("1","2","REAL"), ("2","1","REAL"), ("2","2","REAL"), ("3","3","STRING"), ("3", "4", "STRING"), ("4", "4", "STRING") ]
compatible["-"] = [ ("1","1","INTEGER"), ("1","2","REAL"), ("2","1","REAL"), ("2","2","REAL") ]
compatible["*"] = [ ("1","1","INTEGER"), ("1","2","REAL"), ("2","1","REAL"), ("2","2","REAL") ]
compatible["/"] = [ ("1","1","INTEGER"), ("1","2","REAL"), ("2","1","REAL"), ("2","2","REAL") ]
compatible["DIV"] = [ ("1","1","INTEGER") ]
compatible["MOD"] = [ ("1","1","INTEGER") ]
compatible["<>"] = [ ("1","1","INTEGER"), ("1","2","REAL"), ("2","1","REAL"), ("2","2","REAL"), ("3","3","STRING"), ("3", "4", "STRING"), ("4", "4", "STRING"),
                    ("5", "5", "BOOLEAN") ]
compatible["<"] = compatible["<>"]
compatible[">"] = compatible["<>"]
compatible["="] = compatible["<>"]
compatible["<="] = compatible["<>"]
compatible[">="] = compatible["<>"]
compatible[":="] = [ ("1","1","INTEGER"), ("2","2","REAL"), ("2","1","REAL"), ("3","3","STRING"), ("3","4","STRING"), ("4","4","CHAR"), ("5","5","BOOLEAN") ]
compatible["AND"] = [ ("5","5","BOOLEAN") ]
compatible["OR"] = [ ("5","5","BOOLEAN") ]
compatible["NOT"] = [ ("5","5","BOOLEAN") ]


#clase ambito (engloba proced. y funciones) -> nombre (global), identificadores (variables), otras clases ambitos (tener los enlaces al padre. Llevar el ambito actual y una vez parseado vuelvo al padre). Llevar un ambito con un diccionario de identificadores, cuando hago el uso de una variable me fijo si esta, si no voy al padre hasta q llegue al global.
#clase identificadores (puede ser para los simples y compuestos (arreglos, etc.)) Tener en cuenta arreglos de arreglos
#clase constante
#clase de tipo clase

#Armar una lista de comprobaciones semanticas: cant de parametros en funcion/proced., que lo q retorna sea lo que espero, etc.
#nombre tipo forma(referencia, ..) valoresiniciales

# Crear una variable para llevar el ambito actual
#CREO: que tenemos que meter la clase procedimiento al ambito cuando se realiza un REDUCE del procedure_heading. Y el ambito del procedimiento
# se crea cuando se realiza un reduce del procedure_block (supuestamente ahi metemos los parametros como variables)


class ambito():
    def __init__(self, padre):
        self.dicc_simbolos = {}
        self.padre = padre

    def existe_tipo(self,var):
        """Se fija en el diccionario 'cod_datos' si existe el tipo, de ser asi
        devuelve True de lo contrario devuelve False"""
        if var.clase == "type":
            if not dicc_type.has_key(var.nombre.upper()):
                dicc_type[var.nombre.upper()] = var
            return True
        else:
            try:
                if cod_datos.has_key(var.tipo.upper()) or dicc_type.has_key(var.tipo.upper()):
                    return True
            except:
                return False

    def existe_simbolo(self, var):
        """Se fija en dicc_simbolos si existe el simbolo"""
        if self.dicc_simbolos.has_key(var.nombre.upper()):
            return True
        else:
            return False

    def carga_simbolo(self, var):
        """Primero se fija si existe el simbolo, y si no existe, chequea el tipo, si es valido lo carga"""
        if self.existe_simbolo(var):
            errores = open("ERRORES.txt", "w")
            errores.write('Error: La variable "' + var.nombre + '" ya fue declarada\n')
            errores.close()
            return False
        else:
            if var.clase == "var" or var.clase == "type":
                if self.existe_tipo(var):
                    self.dicc_simbolos[var.nombre.upper()] = var
                    return True
                else:
                    errores = open("ERRORES.txt", "a")
                    errores.write('Error: El tipo de dato "' + var.tipo + '" para la variable "' + var.nombre + '" no existe\n')
                    errores.close()
                    return False
            else:   #si no es una variable, no comprobamos tipo
                self.dicc_simbolos[var.nombre.upper()] = var
                return True

    def busca_simbolo (self, var): #busco el simbolo al cual se hace referencia dentro de la estructura de bloque por ej
        try:
            if self.dicc_simbolos[var.upper()]: # pregunto si esta cargado el simbolo dentro del dicionario del ambito actual
                return self.dicc_simbolos[var.upper()] # si es asi devuelvo el simbolo
        except:
            """Ver esta parte"""
            if self.padre != None: # sino busco en el padre
                lleva_padre = self.padre
                try:
                    if self.padre.dicc_simbolos[var.upper()]: # ver hasta cuando deberiamos buscar? porq si no esta en el padre deberia seguir buscando hacia arriba
                        return self.padre.dicc_simbolos[var.upper()]
                except:
                    while (lleva_padre.padre != None):
                        try:
                            if lleva_padre.padre.dicc_simbolos[var.upper()]:
                                return lleva_padre.padre.dicc_simbolos[var.upper()]
                        except:
                            errores = open("ERRORES.txt", "a")
                            errores.write('Error: Estructura "' + str(var) + '" no declarada\n')
                            errores.close()
                        lleva_padre = lleva_padre.padre
            else:
                errores = open("ERRORES.txt", "a")
                errores.write('Error: Estructura "' + str(var) + '" no declarada\n')
                errores.close()
                return False

    def control_tipo(self,tipo1, tipo2, operacion):
        """Codifica los tipos de datos obtenidos, busca en el diccionario 'compatible'
        si existe la operación, de ser asi chequea que sea válida entre esos tipos"""
        t1 = cod_datos[tipo1.upper()]
        t2 = cod_datos[tipo2.upper()]
        try:
            aux = compatible[operacion.upper()]
            for a in aux:
                if (t1[0], t2[0]) == (a[0],a[1]):
                    nuevo_tipo = a[2]
                    break
                else:
                    return False
            return nuevo_tipo
        except:
            print "OPERACION NO ENCONTRADA"     #Tratar error
#VER DE RETORNAR INTEGER O INT64
    def imprimir(self):
        for x in self.dicc_simbolos:
            if self.dicc_simbolos[x].clase == "var":
                print self.dicc_simbolos[x].nombre + " " + str(self.dicc_simbolos[x].tipo) + str(self.dicc_simbolos[x].dimension)
            elif self.dicc_simbolos[x].clase == "procedure":
                print self.dicc_simbolos[x].nombre + str(self.dicc_simbolos[x].parametros)
            elif self.dicc_simbolos[x].clase == "function":
                print self.dicc_simbolos[x].nombre + str(self.dicc_simbolos[x].parametros) + str(self.dicc_simbolos[x].retorno)
            elif self.dicc_simbolos[x].clase == "type":
                print self.dicc_simbolos[x].nombre + " " + str(self.dicc_simbolos[x].tipo) + str(self.dicc_simbolos[x].atributos)

pepe = ambito(None)
pepe.control_tipo("string", "string", "*")   #Hacer control de None   VER MENOR IGUAL

class procedure(object):
    def __init__(self):
        self.nombre= None
        self.parametros = []
        self.clase = "procedure"

class function(object):
    def __init__(self):
        self.nombre = None
        self.parametros = []
        self.retorno = None
        self.clase = "function"

class array(object):
    def __init__(self):
        self.nombre = None
        self.tipo = None
        self.dimension = None

class variables(object):
    def __init__(self):
        self.nombre = None
        self.tipo = None
        self.dimension = []
        self.clase = "var"

class constante(object):
    def __init__(self):
        self.nombre = None
        self.tipo = None
        self.valor = None
        self.clase = "const"

class record(object):
    def __init__(self):
        self.nombre = None
        self.campos = {}
        self.clase = "record"

class typep(object):
    def __init__(self):
        self.nombre = None
        self.tipo = None
        self.atributos = {}
        self.clase = "type"